Adding Asset Constraints to a System
One of Macro's most powerful features is the ability to enable and disable constraints through input switches without modifying the source code.
This guide documents all available constraints and explains how to enable them in your system.
The prefix before _constraints
(and all other attributes) in the JSON input file varies depending on the component of the asset that the constraint is applied to. Some examples are:
transform_constraints
: constraints on the conversion component of an assetstorage_constraints
: constraints on the storage component of an assetelec_constraints
: constraints on the power input/output component of an assetdischarge_constraints
: constraints on the discharging component of an asset- etc.
Throughout this guide, we show examples with different prefixes to illustrate this variety. When implementing constraints for your specific asset, make sure to review this example case or the asset definition in the Macro Asset Library to make sure you use the correct prefix for your asset type.
Balance Constraint
Note: Enabled by default in all assets in the Macro Asset Library
The balance constraint ensures that the sum of inflows and outflows at any component of an asset equals zero at each time step.
\[\begin{aligned} \sum_{i \in \text{inflows}} \text{flow(i, t)} - \sum_{o \in \text{outflows}} \text{flow(o, t)} = 0 \end{aligned}\]
While enabled by default in all assets in the Macro Asset Library, the user can explicitly enable or disable it by adding these lines to their asset's JSON input file:
{
"transform_constraints": {
"BalanceConstraint": true/false
},
"storage_constraints": {
"BalanceConstraint": true/false
}
}
Capacity Constraint
Note: Enabled by default in all assets in the Macro Asset Library
The capacity constraint ensures that the flow of a commodity through an edge of an asset (e.g, power output) does not exceed the nameplate capacity (multiplied by its availability factor).
For unidirectional edges, the constraint takes this form:
\[\begin{aligned} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
For bidirectional edges:
\[\begin{aligned} \text{sign(e)} \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
where sign(e)
is the sign of the flow of the edge, which is 1
for positive flows and -1
for negative flows.
Unidirectional edges:
\[\begin{aligned} \sum_{t \in \text{time\_interval(e)}} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]
Bidirectional edges:
\[\begin{aligned} \text{sign(e)} \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]
where sign(e)
is the sign of the flow of the edge, which is 1
for positive flows and -1
for negative flows.
The capacity constraint is enabled by default in all assets in the Macro Asset Library. The user can enable/disable it by adding these lines to their asset's JSON input file:
{
"elec_constraints": {
"CapacityConstraint": true/false
}
}
As a reminder, users can define the availability as a time series in the asset's JSON input file using the following format:
- Vector of numbers
{
"availability": [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]
}
- Column in a CSV file
{
"availability": {
"timeseries": {
"path": "system/availability.csv",
"header": "<asset_id>"
}
}
}
Maximum Capacity
The maximum capacity constraint enforces that the capacity of an edge or storage of an asset does not exceed the max_capacity
attribute as specified in the JSON input file.
\[\begin{aligned} \text{capacity(y)} \leq \text{max\_capacity(y)} \end{aligned}\]
where y
is the edge or storage of the asset.
To enable this constraint:
- Add the
MaxCapacityConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
max_capacity
attribute of the asset.
{
"elec_constraints": {
"MaxCapacityConstraint": true
},
"max_capacity": 27760
}
Minimum Capacity
The minimum capacity constraint enforces that the capacity of an edge or storage of an asset is greater than or equal to the min_capacity
attribute as specified in the JSON input file.
\[\begin{aligned} \text{capacity(y)} \geq \text{min\_capacity(y)} \end{aligned}\]
where y
is the edge or storage of the asset.
To enable this constraint:
- Add the
MinCapacityConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
min_capacity
attribute of the asset.
{
"elec_constraints": {
"MinCapacityConstraint": true
},
"min_capacity": 100
}
Minimum Flow Constraint
The minimum flow constraint enforces that the flow of a commodity in an edge does not exceed a user-defined fraction of the capacity of the edge (specified using the min_flow_fraction
attribute).
\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity(e)} \end{aligned}\]
where e
is the edge of the asset.
In the case of edges with unit commitment, the constraint becomes:
\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ucommit(e, t)} \end{aligned}\]
where e
is the edge of the asset, and where capacity_size(e)*ucommit(e, t)
is the total capacity of the edge that is available at time t
.
To enable this constraint:
- Add the
MinFlowConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
min_flow_fraction
attribute of the asset.
{
"elec_constraints": {
"MinFlowConstraint": true
},
"min_flow_fraction": 0.5
}
Minimum Up/Down Time (Unit Commitment)
The minimum up/down time constraint enforces that edges with unit commitment must be on/off for a minimum number of time steps (specified using the min_up_time
/min_down_time
attribute).
\[\begin{aligned} \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_up\_time(e)}-1} \text{ustart(e, t-h)} \end{aligned}\]
\[\begin{aligned} \frac{\text{capacity(e)}}{\text{capacity\_size(e)}} - \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_down\_time(e)}-1} \text{ushut(e, t-h)} \end{aligned}\]
where e
is the edge of the asset.
To enable this constraint:
- Add the
MinUpTimeConstraint
/MinDownTimeConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
min_up_time
/min_down_time
attribute of the asset.
{
"elec_constraints": {
"MinUpTimeConstraint": true,
"MinDownTimeConstraint": true
},
"min_up_time": 6,
"min_down_time": 6
}
This constraint will throw an error if the minimum up/down time is longer than the length of one subperiod.
Must Run Constraint
The must run constraint forces an edge to operate at its full capacity (adjusted by availability) at all times.
\[\begin{aligned} \text{flow(e, t)} = \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
where e
is the edge of the asset.
To enable this constraint:
- Add the
MustRunConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - (optional) Add a time series with the availability of the edge.
{
"elec_constraints": {
"MustRunConstraint": true
},
"availability": {
"timeseries": {
"path": "system/availability.csv",
"header": "<asset_id>"
}
}
}
Ramping Limit Constraint (RampUp/RampDown)
The ramping limits constraint restricts how quickly the flow through an edge can change between consecutive time steps. The maximum rate of change is defined as a fraction of the edge's capacity (ramp_up_fraction
/ramp_down_fraction
).
\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} \leq \text{ramp\_up\_fraction(e)} \times \text{capacity(e)} \end{aligned}\]
\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} \leq \text{ramp\_down\_fraction(e)} \times \text{capacity(e)} \end{aligned}\]
\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} - \text{ramp\_up\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_up\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]
\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} - \text{ramp\_down\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_down\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]
To enable this constraint:
- Add the
RampingLimitConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
ramp_up_fraction
/ramp_down_fraction
attribute of the asset.
{
"elec_constraints": {
"RampingLimitConstraint": true
},
"ramp_up_fraction": 0.6,
"ramp_down_fraction": 0.6
}
Storage Capacity Constraint
Note: Enabled by default for batteries and gas storage assets
This constraint ensures that the storage level of a storage component of an asset never exceeds its total energy capacity.
\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{capacity(g)} \end{aligned}\]
where g
is the storage of the asset.
As mentioned above, this constraint is enabled by default for batteries and gas storage assets. To enable/disable this constraint for other assets, use the following:
- Add the
StorageCapacityConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
.
{
"storage_constraints": {
"StorageCapacityConstraint": true
}
}
Maximum Storage Level
The maximum storage level constraint enforces that the storage level of a storage component of an asset does not exceed the capacity
times the max_storage_level
attribute as specified in the JSON input file.
\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{max\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]
where g
is the storage of the asset.
To enable this constraint:
- Add the
MaxStorageLevelConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
max_storage_level
attribute of the asset.
{
"storage_constraints": {
"MaxStorageLevelConstraint": true
},
"max_storage_level": 1
}
Minimum Storage Level
The minimum storage level constraint enforces that the storage level of a storage component of an asset does not exceed the capacity
times the min_storage_level
attribute as specified in the JSON input file.
\[\begin{aligned} \text{storage\_level(g, t)} \geq \text{min\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]
where g
is the storage of the asset.
To enable this constraint:
- Add the
MinStorageLevelConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
min_storage_level
attribute of the asset.
{
"storage_constraints": {
"MinStorageLevelConstraint": true
},
"min_storage_level": 0.3
}
Storage Charge/Discharge Ratio
The storage charge/discharge ratio constraint links the capacity of the charging edge to the capacity of the discharging edge through the charge_discharge_ratio
input parameter.
\[\begin{aligned} \text{charge\_discharge\_ratio} \times \text{capacity(discharge\_edge)} = \text{capacity(charge\_edge)} \end{aligned}\]
To enable this constraint:
- Add the
StorageChargeDischargeRatioConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
charge_discharge_ratio
attribute of the asset.
{
"storage_constraints": {
"StorageChargeDischargeRatioConstraint": true
},
"storage_charge_discharge_ratio": 0.5
}
As noted above, this constraint is applied to the storage component of the asset, not to the individual charging and discharging edges.
Storage Discharge Limit Constraint
Note: Enabled by default for batteries.
The storage discharge limit constraint ensures that the flow through a discharging edge (adjusted for efficiency) cannot exceed the storage level from the previous time step.
\[\begin{aligned} \frac{\text{flow(discharge\_edge, t)}}{\text{efficiency(discharge\_edge)}} \leq \text{storage\_level(t-1)} \end{aligned}\]
To enable this constraint:
- Add the
StorageDischargeLimitConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
.
{
"discharge_constraints": {
"StorageDischargeLimitConstraint": true
}
}
Storage Maximum/Minimum Duration Constraint
This constraint limits the maximum/minimum energy capacity that can be stored relative to the discharging capacity. The limit is specified in the max_duration
/min_duration
attribute as a number of time steps.
\[\begin{aligned} \text{capacity(storage)} \leq \text{max\_duration} \times \text{capacity(discharge\_edge)} \end{aligned}\]
\[\begin{aligned} \text{capacity(storage)} \geq \text{min\_duration} \times \text{capacity(discharge\_edge)} \end{aligned}\]
To enable this constraint:
- Add the
StorageMaxDurationConstraint
/StorageMinDurationConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
storage_max_duration
/storage_min_duration
attribute of the asset.
{
"storage_constraints": {
"StorageMaximumDurationConstraint": true
},
"storage_max_duration": 10,
"storage_min_duration": 1
}
Storage Symmetric Capacity Constraint
This constraint ensures that for symmetric storage systems, the maximum simultaneous charge and discharge cannot exceed the capacity of the discharging edge.
\[\begin{aligned} \text{flow(e\_discharge, t)} + \text{flow(e\_charge, t)} \leq \text{capacity(e\_discharge)} \end{aligned}\]
To enable this constraint:
- Add the
StorageSymmetricCapacityConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
.
{
"discharge_constraints": {
"StorageSymmetricCapacityConstraint": true
}
}
Minimum Storage Outflow
This constraint is specifically designed for HydroRes
assets. A warning will be issued otherwise.
Tip: If the discharge edge is the only outflow, use the MinFlowConstraint
on the discharge edge instead.
The minimum storage outflow constraint enforces that the sum of the flow through the spillage edge and the discharge edge does not exceed a user-defined fraction of the capacity of the discharge edge (min_outflow_fraction
attribute).
\[\begin{aligned} \text{flow(spillage\_edge, t)} + \text{flow(discharge\_edge, t)} \geq \text{min\_outflow\_fraction} \times \text{capacity(discharge\_edge)} \end{aligned}\]
To enable this constraint:
- Add the
MinStorageOutflowConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
. - Add a value to the
min_outflow_fraction
attribute of the asset.
{
"storage_constraints": {
"MinStorageOutflowConstraint": true
},
"min_outflow_fraction": 0.1
}
Long Duration Storage Implicit Min/Max Constraint
Note: Enabled by default for batteries, gas storage, and hydro reservoirs when long_duration
is set to true
in the asset JSON input file.
This set of constraints manages storage levels for long duration storage systems when modeling representative periods. The implementation is based on the paper: "Improved formulation for long-duration storage in capacity expansion models using representative periods" by Federico Parolin, Paolo Colbertaldo, and Ruaridh Macdonald, 2024 (arXiv:2409.19079). For detailed information about the constraint formulation, please refer to the paper.
This constraint is enabled by default for batteries, gas storage, and hydro reservoirs when long_duration
is set to true
in the asset JSON input file. To enable/disable this constraint for other assets, use the following:
- Add the
LongDurationStorageImplicitMinMaxConstraint
to the list of constraints in the JSON input file of the asset and set it totrue
.
{
"storage_long_duration": true,
"storage_constraints": {
"LongDurationStorageImplicitMinMaxConstraint": true/false
}
}